package com.acme.chat.server;

import static com.acme.chat.settings.Settings.LOCALE_BUNDLE;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;

import com.acme.chat.client.ChatClient;
import com.acme.chat.db.PrivateManager;
import com.acme.chat.message.ChatMessage;
import com.acme.chat.message.Message;
import com.acme.chat.message.MessageType;
import com.acme.chat.util.Utils;

// TODO: Change all 'users' on 'clients' or 'clients' on 'users', for consistency
/**
 * Implementation of Server interface. This implementation knows nothing about tcp or rmi.
 */
public final class ServerImpl implements Server {

    private static final int AVERAGE_NAME_LENGTH = 25;

    private static final String USER = LOCALE_BUNDLE.getString("User");
    private static final String CONNECTED_TO_ROOM = LOCALE_BUNDLE.getString("ConnectedToRoom");
    private static final String CONNECTED_TO_ROOM2 = LOCALE_BUNDLE.getString("Connected");
    private static final String DISCONNECT_FROM_ROOM = LOCALE_BUNDLE.getString("DisconnectFromRoom");
    private static final String ERROR_USER_EXISTS = LOCALE_BUNDLE.getString("ErrUserExist");
    private static final String ERROR_TOO_LONG_NAME = LOCALE_BUNDLE.getString("ErrToLongName") + ChatClient.MAX_NAME_LENGTH + "\n";
    private static final String MESSAGE_LIST_OF_THE_USERS_IN_THE_ROOM = LOCALE_BUNDLE.getString("RoomsUsersList");
    private static final String MESSAGE_LIST_OF_THE_USERS_ON_THE_SERVER = LOCALE_BUNDLE.getString("ServersUsersList");

    private static final String LEFT_ARROW = " < ";
    private static final String RIGHT_ARROW = " > ";

    private static Logger serverLogger = Logger.getLogger(ServerImpl.class.getName());

    private static ServerImpl server = new ServerImpl();

    private Set<ChatClient> clients;
    private Map<String, Set<ChatClient>> clientsInTheRoom;

    private PrivateManager privateManager;

    private ServerImpl() {
        clients = new HashSet<ChatClient>();
        clientsInTheRoom = new HashMap<String, Set<ChatClient>>();
        privateManager = new PrivateManager();
    }

    public static ServerImpl getInstance() {
        return server;
    }

    @Override
    public synchronized boolean addClient(ChatClient client) {
        if (userExists(client)) {
            Message message = new ChatMessage(MessageType.ERROR, ERROR_USER_EXISTS + getAllUserList());
            client.sendMessage(message);
            disconnectClient(client);
            return false;
        }

        if (nameIsTooLong(client)) {
            Message message = new ChatMessage(MessageType.ERROR, ERROR_TOO_LONG_NAME);
            client.sendMessage(message);
            disconnectClient(client);
            return false;
        }

        if (clients.add(client) && addClientToRoom(client)) {
            Message message = new ChatMessage(MessageType.MESSAGE, getUserConnectedString(client));
            sendMessageInRoom(client.getRoom(), message);
            print(CONNECTED_TO_ROOM2 + client.getName());
            return true;
        }

        return false;
    }

    private boolean addClientToRoom(ChatClient client) {
        Set<ChatClient> roomClients = getClientsFromRoom(client.getRoom());
        return roomClients.add(client);
    }

    private Set<ChatClient> getClientsFromRoom(String room) {
        Set<ChatClient> roomClients = clientsInTheRoom.get(room);
        if (roomClients == null) {
            roomClients = new HashSet<ChatClient>();
            clientsInTheRoom.put(room, roomClients);
        }
        return roomClients;
    }

    /**
     * Return true if the another user with this name already connected to this
     * server.
     *
     * @param client
     * @return
     */
    private boolean userExists(ChatClient client) {
        return clients.contains(client);
    }

    private boolean nameIsTooLong(ChatClient client) {
        return client.getName().length() > ChatClient.MAX_NAME_LENGTH;
    }

    /**
     * Method removes client from the server
     *
     * @param client
     */
    @Override
    public synchronized boolean removeClient(ChatClient client) {
        removeClientFromRoom(client);
        return clients.remove(client);
    }

    private void removeClientFromRoom(ChatClient client) {
        String room = client.getRoom();
        Set<ChatClient> chatClients = clientsInTheRoom.get(room);
        if (chatClients != null) {
            chatClients.remove(client);
        }
    }

    @Override
    public synchronized void processMessage(Message message, ChatClient client) {
        switch (message.getType()) {
            case MESSAGE:
                sendCommonMessageFromClient(message, client);
                break;
            case LOGOUT:
                disconnectClient(client);
                break;
            case WHOISIN:
                sendListOfUsers(client);
                break;
            case PRIVATE:
                privateManager.sendPrivateMessage(client, getClientByName(message.getNameToWho()), message.getNameToWho(), message);
                break;
            case GET_HISTORY:
                privateManager.sendHistory(client);
                break;
            case CHANGE_ROOM:
                changeRoom(message, client);
                break;
            default:
                break;
        }
    }

    private ChatClient getClientByName(String name) {
        for (ChatClient client : clients) {
            if (client.getName().equals(name)) {
                return client;
            }
        }
        return null;
    }

    private synchronized void changeRoom(Message message, ChatClient client) {
        String oldRoom = client.getRoom();
        String newRoom = message.getRoom();
        sendMessageInRoom(oldRoom, new ChatMessage(MessageType.MESSAGE, getUserDisconnectedString(client)));
        print(getUserDisconnectedString(client));
        removeClientFromRoom(client);
        client.changeRoom(newRoom);
        addClientToRoom(client);
        sendMessageInRoom(newRoom, new ChatMessage(MessageType.MESSAGE, getUserConnectedString(client)));
        print(getUserConnectedString(client));
    }

    private void sendCommonMessageFromClient(Message message, ChatClient client) {
        String composeString = Utils.getCurrentTimeFormatted() + ": " + client.getName() + ": " + message.getMessage();
        Message composeMessage = new ChatMessage(MessageType.MESSAGE, composeString);
        sendMessageInRoom(client.getRoom(), composeMessage);
        print(composeString);
    }

    private void sendMessageInRoom(String room, Message message) {
        Set<ChatClient> roomClients = clientsInTheRoom.get(room);
        if (roomClients == null) {
            return;
        }
        for (ChatClient client : roomClients) {
            client.sendMessage(message);
        }
    }

    private void disconnectClient(ChatClient client) {
        removeClient(client);
        client.disconnect();
        sendMessageInRoom(client.getRoom(), new ChatMessage(MessageType.MESSAGE, getUserDisconnectedString(client)));
        print(getUserDisconnectedString(client));
    }

    private String getUserConnectedString(ChatClient client) {
        return USER + LEFT_ARROW + client.getName() + RIGHT_ARROW + CONNECTED_TO_ROOM + LEFT_ARROW + client.getRoom() + RIGHT_ARROW;
    }

    private String getUserDisconnectedString(ChatClient client) {
        return USER + LEFT_ARROW + client.getName() + RIGHT_ARROW + DISCONNECT_FROM_ROOM + LEFT_ARROW + client.getRoom() + RIGHT_ARROW;
    }

    private void sendListOfUsers(ChatClient client) {
        String userList = getUserListFromTheRoom(client.getRoom());
        Message message = new ChatMessage(MessageType.MESSAGE, userList);
        client.sendMessage(message);
    }

    private String getUserListFromTheRoom(String room) {
        Set<ChatClient> roomClients = getClientsFromRoom(room);
        StringBuilder sb = new StringBuilder(roomClients.size() * AVERAGE_NAME_LENGTH);
        sb.append(MESSAGE_LIST_OF_THE_USERS_IN_THE_ROOM).append(room).append(":\n");
        for (ChatClient client : roomClients) {
            sb.append("< ").append(client.getName()).append(" >\n");
        }
        return sb.toString();
    }

    private String getAllUserList() {
        StringBuilder sb = new StringBuilder(clients.size() * AVERAGE_NAME_LENGTH);
        sb.append(MESSAGE_LIST_OF_THE_USERS_ON_THE_SERVER);
        for (ChatClient client : clients) {
            sb.append("< ").append(client.getName()).append(" >\n");
        }
        return sb.toString();
    }
    private void print(String message){
    	serverLogger.info(message);
    }

}
